package com.body.park.gateway.filters;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.body.park.common.response.ResponseEntity;
import com.body.park.gateway.entity.AuthToken;
import com.body.park.gateway.entity.User;
import com.body.park.gateway.enums.ResponseCodeEnum;
import com.body.park.gateway.utils.JwtUtils;
import com.body.park.gateway.utils.RedisCache;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;

/**
 * @Author chaox-wu
 * @Date 2022/12/2
 * @Description:
 */
@Slf4j
@Component
public class GateWayGloFilter implements GlobalFilter, Ordered {

    @Resource(type = RedisCache.class)
    private RedisCache redisCache;

    @Resource(type = RestTemplate.class)
    private RestTemplate restTemplate;

    public static final String AUTHORIZATION = "Authorization";

    public static final String REFRESH_TOKEN = "refreshToken";

    private static final String SERVICE_URL = "http://park-auth-service/refresh/token";

    private static final long EXPIRE_TIME = 60L;

    /**
     * 白名单
     */
    public static final String[] NO_AUTH_PATH = {"/auth/password/login", "/auth/oauth/check_token", "/auth/oauth/token"};

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //获取response对象
        ServerHttpResponse response = exchange.getResponse();
        //获取request对象
        ServerHttpRequest request = exchange.getRequest();
        //没有登录 判断是否是白名单请求
        if (isAllowRequest(request)) {
            //如果是 放行
            return chain.filter(exchange);
        }
        //不是白名单路径
        try {
            //获取header中的token
            List<String> tokenList = request.getHeaders().get(AUTHORIZATION);
            if (CollectionUtils.isEmpty(tokenList)) {
                //没有token,拦截请求
                return getVoidMono(response, ResponseCodeEnum.TOKEN_MISSION);
            }
            String token = tokenList.get(0);
            //如果当前token 不在 redis里面 那就证明当前 token是假的 token
            //如果是假的token 那么请你去认证服务器认证去
            Object tokenObj = redisCache.get(token);
            if (Objects.isNull(tokenObj) || StrUtil.isBlank(tokenObj.toString())) {
                //把异常返回过去
                return getVoidMono(response, ResponseCodeEnum.TOKEN_INVALID);
            }
            //获取key剩余时间
            long expireTime = redisCache.getKeyExpire(token);
            //如果小于60s，则刷新自动刷新token
            if (expireTime < EXPIRE_TIME) {
                List<String> refreshTokenList = request.getHeaders().get(REFRESH_TOKEN);
                if (!CollectionUtils.isEmpty(refreshTokenList)) {
                    String refreshToken = refreshTokenList.get(0);
                    AuthToken authToken = refreshToken(refreshToken);
                    if (Objects.nonNull(authToken)) {
                        String authTokenStr = JSONUtil.toJsonStr(authToken);
                        //删除Redis原有的令牌 并存入新的令牌
                        redisCache.delete(token);
                        redisCache.set(authToken.getAccess_token(), authTokenStr, 3600L);
                        //将令牌响应给前端
                        return returnsToken(response, authToken);
                    }
                }
            }
            User userJwt = JwtUtils.getUserJwtFromToken(token);
            log.info("用户：{}，正在访问资源：{}", userJwt.getName(), request.getPath());
            //增强请求头
            request.mutate().header(AUTHORIZATION, "Bearer " + token, "token", token);
            return chain.filter(exchange);
        } catch (Exception e) {
            log.info("服务解析用户信息失败:", e);
            //内部异常 返回500
            exchange.getResponse().setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
            return exchange.getResponse().setComplete();
        }
    }

    @Override
    public int getOrder() {
        return -1;
    }

    /**
     * 判断请求是否在白名单
     *
     * @param request
     * @return
     */
    private boolean isAllowRequest(ServerHttpRequest request) {
        //获取当前请求的path和method
        String path = request.getPath().toString();
        String method = request.getMethodValue();
        //判断是否允许
        if (Arrays.asList(NO_AUTH_PATH).contains(path)) {
            //是许可的路径 放行
            return true;
        }
        return false;
    }

    /**
     * 创建response返回提示信息
     *
     * @param serverHttpResponse
     * @param responseCodeEnum
     * @return
     */
    private Mono<Void> getVoidMono(ServerHttpResponse serverHttpResponse, ResponseCodeEnum responseCodeEnum) {
        serverHttpResponse.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        ResponseEntity responseResult = ResponseEntity.failed(responseCodeEnum.getCode(), responseCodeEnum.getMessage());
        DataBuffer dataBuffer = serverHttpResponse.bufferFactory().wrap(JSONUtil.toJsonStr(responseResult).getBytes());
        return serverHttpResponse.writeWith(Flux.just(dataBuffer));
    }

    /**
     * 创建response返回刷新令牌
     *
     * @param serverHttpResponse
     * @param obj
     * @return
     */
    private Mono<Void> returnsToken(ServerHttpResponse serverHttpResponse, Object obj) {
        serverHttpResponse.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        DataBuffer dataBuffer = serverHttpResponse.bufferFactory().wrap(JSONUtil.toJsonStr(obj).getBytes());
        return serverHttpResponse.writeWith(Flux.just(dataBuffer));
    }

    /**
     * 刷新token
     * @return
     */
    private AuthToken refreshToken(String token) {
        JSONObject jsonObject = new JSONObject();
        jsonObject.set("token", token);
        //2、添加请求头
        HttpHeaders headers = new HttpHeaders();
        headers.add("Content-Type","application/json");
        HttpEntity<String> formEntity = new HttpEntity<>(JSONUtil.toJsonStr(jsonObject), headers);
        return restTemplate.postForObject(SERVICE_URL, formEntity, AuthToken.class);
    }
}
